// Implementation of receiverHelper for models
// Copyright 2004 (C) Ralph Thomas

#include <model/internal/receiverHelper.h>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <toolkit/application.h>
#include <map>
#include <vector>
#include <algorithm>

namespace model {
	class rhImpl {
	  public:
		//
		/// The notification class is used to ensure that notifications
		/// arrive in the correct thread.
		//
		class notification {
		  public:
			notification( const model* m, const std::string& a ) :
				mod( m ), attr( a ) {}

			const model* mod;
			std::string attr;
		};

		typedef std::vector<receiver*> rlist;
		typedef std::vector<notification> nlist;

		rlist rs;
		nlist ns;
		boost::mutex mutex;
		typedef boost::mutex::scoped_lock lock;
		//
		// void process()
		//
		/// This process method issues all of the notifications in to
		/// the main thread. It performs appropriate locking to prevent
		/// problems.
		//
		void process() {
			rlist::iterator i;
			nlist::iterator ni;
			//
			// We make a local copy of rlist and iterate through
			// that, checking that something hasn't been removed
			// from the actual rlist each iteration.
			//
			lock lk(mutex);
			rlist rscopy = rs;
			//
			// We make a copy of the list of notifications and
			// then clear it out. This means that all of the
			// notifications currently in the list will get
			// delivered.
			//
			nlist nscopy = ns; ns.clear();
			lk.unlock();
			//
			// Verify that we have some notifications to deliver
			// before getting into the more complicated stuff.
			//
			if( nscopy.empty() ) return;
			//
			// Iterate through the copied list, checking the
			// real list each iteration to ensure that we won't
			// call a deleted receiver.
			//
			for( i = rscopy.begin(); i != rscopy.end(); i++ ) {
				//
				// This looks maximally inefficient -- doing
				// a find on rs n*m times each loop through.
				// It probably is, but it also might be
				// required: If some receiver gets the first
				// notification in our list and then decides
				// to unsubscribe then we had better not send
				// it the rest of the notifications in our
				// list. So we *have* to check that the
				// receiver is still in the list of receivers
				// for each notification and receiver.
				//
				for( ni = nscopy.begin(); ni != nscopy.end();
				     ni++ ) {
					//
					// Verify that we can send to this
					// receiver, then send.
					//
					lock lk( mutex );
					bool canSend = (std::find( rs.begin(),
						rs.end(), *i ) != rs.end());
					lk.unlock();
					if( canSend )
						(*i)->notify( ni->mod, ni->attr );
				}
			}
		}
	};
	typedef boost::shared_ptr<rhImpl> impl_ptr_t;
	typedef boost::weak_ptr<rhImpl> impl_weak_ptr_t;

	receiverHelper::receiverHelper() : _impl( new rhImpl ) {}
	receiverHelper::~receiverHelper() {}
	void receiverHelper::subscribe( receiver* r ) {
		if( !r ) return;
		rhImpl::lock lk( _impl->mutex );
		_impl->rs.push_back( r );
	}
	void receiverHelper::unsubscribe( const receiver* r ) {
		rhImpl::rlist::iterator i;

		rhImpl::lock lk( _impl->mutex );
		for( i = _impl->rs.begin(); i != _impl->rs.end(); i++ )
			if( *i == r ) {
				_impl->rs.erase( i );
				return;
			}
	}
	//
	// This is a band-aid fix around a much larger problem of how to handle
	// notifications that haven't been sent at the time the model dies.
	//
	static void handleNotify( impl_weak_ptr_t weakref ) {
		impl_ptr_t impl( weakref.lock() );
		if( !impl ) return;
		impl->process();
	}
	void receiverHelper::notify( const model* m, const std::string& attrName ) {
		//
		// We don't actually notify here, we just lock the list of
		// pending notifications and add to that.
		//
		rhImpl::lock lk( _impl->mutex );
		_impl->ns.push_back( rhImpl::notification( m, attrName ) );
		//
		// Request that the application call the impl's process method
		// ASAP to get this message delivered to the main thread.
		//
		impl_weak_ptr_t weakref( _impl );
		toolkit::application::getInstance().executeInMain( boost::bind( &handleNotify, weakref ) );
	}
};

